#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Castle.MicroKernel;
using Castle.Windsor;
using Castle.Windsor.Installer;
using Grawlix.Concurrency;
using Grawlix.Geometry;
using Grawlix.Services;
using Grawlix.Services.Rendering;
using log4net;
using OpenTK;
using OpenTK.Platform;
using Rhino.Commons.Binsor;

namespace Grawlix.Client
{
	/// <summary>
	/// The main game window used to interact with the user.
	/// </summary>
	public class ClientWindow : GameWindow
	{
		/// <summary>
		/// The log used to sent output information.
		/// </summary>
		private static readonly ILog _log =
			LogManager.GetLogger(typeof(ClientWindow));

		/// <summary>
		/// Whether this object has yet been disposed.
		/// </summary>
		private bool _disposed;

		/// <summary>
		/// The translator between actual keys and game keys.
		/// </summary>
		private InputTranslator _input;

		/// <summary>
		/// An abstraction that handles game loading.
		/// </summary>
		private IGameLoader _loader;

		/// <summary>
		/// The screen renderer.
		/// </summary>
		private IRenderDevice _render;

		/// <summary>
		/// The last known viewport of the renderer.
		/// </summary>
		private Rect _viewport;

		/// <summary>
		/// The kernel providing services from this local kernel.
		/// </summary>
		private IWindsorContainer _windsor;

		/// <summary>
		/// Gets the input translator device.
		/// </summary>
		/// <value>The input translator device.</value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public InputTranslator Input
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("ClientWindow");
				return _input;
			}
		}

		/// <summary>
		/// Gets the render device for the window.
		/// </summary>
		/// <value>The render device for the window.</value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public IRenderDevice RenderDevice
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("ClientWindow");
				return _render;
			}
		}

		/// <summary>
		/// Disposes of the <see cref="GameWindow"/>, releasing all
		/// resources consumed by it.
		/// </summary>
		public override sealed void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed
		/// and unmanaged resources; <c>false</c> to release only
		/// unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{
					_render = null;
					_loader = null;
					if (_windsor != null)
					{
						IWindsorContainer windsor = _windsor;
						_windsor = null;
						windsor.Dispose();
					}
					if (_input != null)
					{
						var temp = (IDisposable)_input;
						_input = null;
						temp.Dispose();
					}
				}
				// NOTE: Workaround OpenTK dispose pattern.
				try
				{
					base.Dispose();
				}
				catch (ObjectDisposedException)
				{
				}
				_disposed = true;
			}
		}

		/// <summary>
		/// Occurs after establishing an <c>OpenGL</c> context, but
		/// before entering the main loop. Override to load resources
		/// that should be maintained for the lifetime of the
		/// application.
		/// </summary>
		/// <param name="e">Not used.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"This class must stop exceptions from propagating " +
				"to the main entry point.")]
		public override void OnLoad(EventArgs e)
		{
			if (_disposed)
				throw new ObjectDisposedException("ClientWindow");
			try
			{
				const string file =
					"Properties/ClientServiceConfiguration.boo";
				_windsor = new WindsorContainer("Client",
				                                new DefaultKernel(),
				                                new DefaultComponentInstaller
				                                	());
				BooReader.Read(_windsor, file);

				_render = _windsor.Resolve<IRenderDevice>();
				_render.Clear();
				_render.Finish();
				SwapBuffers();

				_input = new InputTranslator(this);
				_loader = _windsor.Resolve<IGameLoader>();
				_loader.Activate(_windsor);
				OnResize(new ResizeEventArgs(
				         	(int)_viewport.Dimensions.X,
				         	(int)_viewport.Dimensions.Y));
			}
			catch (Exception exception)
			{
				ExitAsync();
				if (_log.IsFatalEnabled)
				{
					_log.Fatal(
						"Exiting due to exception during client load.",
						exception);
				}
			}
		}

		/// <summary>
		/// Override in derived classes to render a frame.
		/// </summary>
		/// <param name="e">Contains information necessary for frame
		/// rendering.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"This class must stop exceptions from propagating " +
				"to the main entry point.")]
		public override void OnRenderFrame(RenderFrameEventArgs e)
		{
			if (_disposed)
				throw new ObjectDisposedException("ClientWindow");
			try
			{
				if (_loader.HasQuit)
				{
					if (!IsExiting)
					{
						base.ExitAsync();
						if (_log.IsInfoEnabled)
							_log.Info("Quit noticed on render, exiting.");
					}
				}
				else if (_loader.CurrentKernel != null)
				{
					Domain main = _loader.CurrentKernel.Main;
					Debug.Assert(main.Ready && main.Failure == null);
					_render.Clear();
					main.DirectAccess<IRenderableArea>().Render(_render,
					                                            _viewport);
					_render.Finish();
					SwapBuffers();
				}
				else
				{
					_render.Clear();
					_render.Finish();
					SwapBuffers();
				}
			}
			catch (Exception exception)
			{
				_loader.LoadLocal();
				if (_log.IsFatalEnabled)
				{
					_log.Fatal(
						"Quiting due to exception on client render.",
						exception);
				}
			}
		}

		/// <summary>
		/// Override in derived classes to respond to the Resize
		/// events.
		/// </summary>
		/// <param name="e">Contains information about the Resize
		/// event.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"This class must stop exceptions from propagating " +
				"to the main entry point.")]
		protected override void OnResize(ResizeEventArgs e)
		{
			if (_disposed)
				throw new ObjectDisposedException("ClientWindow");
			try
			{
				var dimensions = new Vector2(Math.Abs(e.Width),
				                             Math.Abs(e.Height));
				_viewport = new Rect(new Vector2(0, 0), dimensions);
				if (_log.IsDebugEnabled)
				{
					_log.Debug("Resizing screen to " + dimensions.X + "x" +
					           dimensions.Y + ".");
				}
				if (_render != null)
				{
					_render.Resize(dimensions);
					_render.Clear();
					_render.Finish();
					SwapBuffers();
				}
			}
			catch (Exception exception)
			{
				if (_loader != null)
					_loader.LoadLocal();
				if (_log.IsFatalEnabled)
				{
					_log.Fatal(
						"Quiting due to exception on client resize.",
						exception);
				}
			}
		}

		/// <summary>
		/// Occurs after after calling <see cref="GameWindow.Exit"/>,
		/// but before destroying the <c>OpenGL</c> context. Override
		/// to unload application resources.
		/// </summary>
		/// <param name="e">Not used.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"This class must stop exceptions from propagating " +
				"to the main entry point.")]
		public override void OnUnload(EventArgs e)
		{
			if (_disposed)
				throw new ObjectDisposedException("ClientWindow");
			try
			{
				if (_windsor != null)
					_windsor = null;
				if (_render != null)
					_render = null;
				if (_input != null)
				{
					var temp = (IDisposable)_input;
					_input = null;
					temp.Dispose();
				}
				if (_loader != null)
					_loader = null;
			}
			catch (Exception exception)
			{
				if (_log.IsWarnEnabled)
					_log.Warn("Exception during client unload.", exception);
			}
		}

		/// <summary>
		/// Override in derived classes to update a frame.
		/// </summary>
		/// <param name="e">Contains information necessary for frame
		/// updating.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification =
				"This class must stop exceptions from propagating " +
				"to the main entry point.")]
		public override void OnUpdateFrame(UpdateFrameEventArgs e)
		{
			if (_disposed)
				throw new ObjectDisposedException("ClientWindow");
			try
			{
				if (_loader.HasQuit)
				{
					if (!IsExiting)
					{
						base.ExitAsync();
						if (_log.IsInfoEnabled)
							_log.Info("Quit noticed on update, exiting.");
					}
				}
				else
				{
					TimeSpan elapsed = TimeSpan.FromSeconds(e.Time);
					_loader.Update(elapsed);
					if (_loader.CurrentKernel != null)
					{
						var scatter = new FuturePool();
						foreach (var domain in _loader.CurrentKernel.Domains)
						{
							Domain currentDomain = domain;
							scatter.Add(() => currentDomain.Update(elapsed));
						}
						scatter.Wait();
					}
				}
			}
			catch (Exception exception)
			{
				_loader.LoadLocal();
				if (_log.IsFatalEnabled)
				{
					_log.Fatal(
						"Quiting due to exception on client update.",
						exception);
				}
			}
		}
	}
}